package ws.jonas.common.log;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import ws.jonas.common.config.Config;

/**
 * Logs every message of level >= logLevel.
 */
public class Logger {
	private static final String MAIL_RECIPIENT = "sybosc@jonas.ws";
	private static final String MAIL_SUBJECT = "Sybosc log";
	private static final String MAIL_BODY_START = "The following messages were logged while running the Sybosc:\n\n";
	
	private static StringBuilder mailLog = new StringBuilder(MAIL_BODY_START);
	private static HashMap<Class<?>, Logger> loggers = new HashMap<Class<?>, Logger>();
	
	public enum LogLevel { ALL, FINE, INFO, WARNING, SEVERE, OFF };
	
	private LogLevel logLevel;
	private LogLevel mailLevel;
	private SimpleDateFormat formatter;
	private Class<?> owner;
	
	public static Logger getInstance(Class<?> owner) {
		Logger log = loggers.get(owner);
		if (log == null) {
			log = new Logger(owner);
		}
		return log;
	}
	
	public static void setLevels(String level) throws IllegalArgumentException {
		LogLevel l = LogLevel.valueOf(level.toUpperCase());
		for (Logger log : loggers.values()) {
			log.setLevel(l);
		}
	}
	
	public static void setMailLevels(String level) throws IllegalArgumentException {
		LogLevel l = LogLevel.valueOf(level.toUpperCase());
		for (Logger log : loggers.values()) {
			log.setMailLevel(l);
		}
	}
	
	private Logger(Class<?> owner) {
		this(owner, LogLevel.FINE, LogLevel.SEVERE);
	}
	
	private Logger(Class<?> owner, LogLevel logLevel, LogLevel mailLevel) {
		this.owner = owner;
		this.logLevel = logLevel;
		this.mailLevel = mailLevel;
		formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		loggers.put(owner, this);
	}
	
	public LogLevel getLevel() {
		return logLevel;
	}
	
	public void setLevel(LogLevel l) {
		logLevel = l;
	}
	
	public LogLevel getMailLevel() {
		return mailLevel;
	}
	
	public void setMailLevel(LogLevel l) {
		mailLevel = l;
	}
	
	public void fine(String msg) {
		log(LogLevel.FINE, msg);
	}
	
	public void fine(Exception e) {
		log(LogLevel.FINE, e);
	}
	
	public void info(String msg) {
		log(LogLevel.INFO, msg);
	}
	
	public void info(Exception e) {
		log(LogLevel.INFO, e);
	}
	
	public void warning(String msg) {
		log(LogLevel.WARNING, msg);
	}
	
	public void warning(Exception e) {
		log(LogLevel.WARNING, e);
	}
	
	public void severe(String msg) {
		log(LogLevel.SEVERE, msg);
	}
	
	public void severe(Exception e) {
		log(LogLevel.SEVERE, e);
	}
	
	public void log(LogLevel l, Exception e) {
		if (!validateLogLevel(l)) {
			return;
		}
		StringBuilder sb = getFormattingText(l);
		sb.append(System.getProperty("line.separator"));
		sb.append(e.toString() + System.getProperty("line.separator"));
		StackTraceElement[] ste = e.getStackTrace();
		for (int i = 0; i < ste.length; ++i) {
			sb.append("\t" + ste[i] + System.getProperty("line.separator"));
		}
		process(l, sb.toString());
	}
	
	public void log(LogLevel l, String msg) {
		if (!validateLogLevel(l)) {
			return;
		}
		StringBuilder sb = getFormattingText(l);
		sb.append(msg);
		process(l, sb.toString());
	}
	
	public void sendMails() {
		if (mailLog.length() <= MAIL_BODY_START.length()) {
			return;
		}
		
		final String server = Config.get(Config.SMTP_SERVER_KEY);
		final String port = Config.get(Config.SMTP_PORT_KEY);
		final String sender = Config.get(Config.SMTP_SENDER_KEY);
		final String user = Config.get(Config.SMTP_USER_KEY);
		final String password = Config.get(Config.SMTP_PASSWORD_KEY);
		
		if (server != null && port != null && sender != null && !server.equals("") && !port.equals("") && !sender.equals("")) {
			try {
				Properties props = new Properties();
				props.put("mail.smtp.host", server);
				props.put("mail.smtp.user", user);
				props.put("mail.smtp.port", port);
				props.put("mail.smtp.socketFactory.port", port);
				if (Boolean.parseBoolean(Config.get(Config.SSL_USAGE_ENABLED))) {
					props.put("mail.smtp.auth", "true");
					props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
				}
				Session session = Session.getInstance(props, new Authenticator() {
					protected PasswordAuthentication getPasswordAuthentication() {
						return new PasswordAuthentication(user, password);
					}
				});
				Message msg = new MimeMessage(session);
				msg.setFrom(new InternetAddress(sender));
				String mailRecipients = MAIL_RECIPIENT + ", " + Config.get(Config.MAIL_RECIPIENTS_KEY);
				msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(mailRecipients));
				msg.setSubject(MAIL_SUBJECT);
				msg.setText(mailLog.toString());
				msg.setSentDate(new Date());
				Transport.send(msg);
				System.out.println(getFormattingText(LogLevel.INFO) + "Log mails sent");
			} catch (Exception e) {
				System.err.println(getFormattingText(LogLevel.SEVERE) + "Unable to send log mails");
				e.printStackTrace(System.err);
			}
		} else {
			System.out.println(getFormattingText(LogLevel.INFO) + "Did not send log mails, the SMTP settings are not properly setup");
		}
		mailLog = new StringBuilder(MAIL_BODY_START);
	}
	
	private void process(LogLevel l, String msg) {
		switch (l) {
			case SEVERE:
				System.err.println(msg);
				break;
			case WARNING:
				System.err.println(msg);
				break;
			case INFO:
				System.out.println(msg);
				break;
			case FINE:
				System.out.println(msg);
				break;
			default:
				throw new IllegalArgumentException("Invalid LogLevel");
		}
		if (validateMailLogLevel(l)) {
			mailLog.append(msg + "\n");
		}
	}
	
	/**
	 * Returns true if the level is high enough to be logged.
	 */
	private boolean validateLogLevel(LogLevel l) {
		return l.compareTo(logLevel) >= 0;
	}
	
	private boolean validateMailLogLevel(LogLevel l) {
		return l.compareTo(mailLevel) >= 0;
	}
	
	private StringBuilder getFormattingText(LogLevel l) {
		StringBuilder sb = new StringBuilder(100);
		sb.append("[" + formatter.format(Calendar.getInstance().getTime()) + "]");
		sb.append(" " + owner + " - [");
		sb.append(l.toString() + "]: ");
		return sb;
	}
}
